package jsonrpc

import (
	"encoding/json"
	"fmt"
	"lib/exception"
	"reflect"
)

type RpcCommand func(param interface{}) (interface{}, exception.Exception)
type RpcCommandParamDecoder func(raw json.RawMessage) (interface{}, exception.Exception)

type RpcHandler struct {
	Command RpcCommand
	Decoder RpcCommandParamDecoder
}

type SingleRequest struct {
	JsonRpc   string          `json:"jsonRpc"`
	Namespace string          `json:"namespace"`
	Method    string          `json:"method"`
	Params    json.RawMessage `json:"params"`
	Id        uint            `json:"id"`
}

type SingleResponse struct {
	JsonRpc   string              `json:"jsonRpc"`
	Namespace string              `json:"namespace"`
	Method    string              `json:"method"`
	Result    interface{}         `json:"result"`
	Error     SingleResponseError `json:"error"`
	Id        uint                `json:"id"`
}

type SingleResponseError struct {
	Code    uint   `json:"code"`
	Message string `json:"message"`
}

func NewHandler(cmd RpcCommand, paramType reflect.Type) RpcHandler {
	decoder := newRpcCommandParamDecoder(paramType)
	return RpcHandler{Command: cmd, Decoder: decoder}
}

func newRpcCommandParamDecoder(paramType reflect.Type) RpcCommandParamDecoder {
	return func(raw json.RawMessage) (interface{}, exception.Exception) {
		instance := reflect.New(paramType).Interface()
		unmarshalErr := json.Unmarshal(raw, instance)
		if unmarshalErr != nil {
			return nil, unmarshalErr
		}
		return instance, nil
	}
}

func MethodNotFound(methodName string) SingleResponseError {
	return SingleResponseError{
		Code:    500,
		Message: fmt.Sprintf("Function %v not found.", methodName),
	}
}

func MethodParamDecodeError(methodName string, err exception.Exception) SingleResponseError {
	return SingleResponseError{
		Code:    500,
		Message: fmt.Sprintf("Could not decode param for method %v. Err is %v.", methodName, err),
	}
}

func BadRequestContent() SingleResponseError {
	return SingleResponseError{
		Code:    500,
		Message: "Invalid value for content-type or body, should be application/json.",
	}
}

func BuildErrorResponse(request SingleRequest, responseErr SingleResponseError) SingleResponse {
	return SingleResponse{
		Id:        request.Id,
		JsonRpc:   request.JsonRpc,
		Namespace: request.Namespace,
		Result:    nil,
		Error:     responseErr,
	}
}

func BuildResponse(request SingleRequest, result interface{}) SingleResponse {
	return SingleResponse{
		Id:        request.Id,
		JsonRpc:   request.JsonRpc,
		Namespace: request.Namespace,
		Result:    result,
		Error:     SingleResponseError{},
	}
}
